<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>[2.17]-微服务：Api接口服务层 | PhalApi(π框架) - PHP轻量级开源接口框架 - 助你创造价值！</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=no" name="format-detection">
    <meta content="email=no" name="format-detection">
    <meta name="Author" content="PhalApi,ecitlm,dogstar">
    <meta name="description" content="[2.17]-微服务：Api接口服务层，PhalApi是一个PHP轻量级开源接口框架，致力于快速开发接口服务。支持HTTP/SOAP/RPC等协议，可用于搭建接口/微服务/RESTful接口/Web Services。我们不断更新，保持生气；为接口负责，为开源负责！并承诺永久免费！">
    <meta name="keywords" content="PhalApi,phalapi,phalapi接口开发,后台接口开发,后台接口开发框架,接口开发,接口框架,开源接口框架,PHP后台接口开发,PHP接口开发,PHP接口框架,PHP后台接口框架,phalapi官网,PHP接口框架,php接口开发框架,php接口开发,php web框架,π框架,π开发框架,π接口框架,π接口开发框架,api接口,php接口,,PHP接口框架,phalapi文档,phalapi wiki,PhalApi文档,phalapi在线文档,phalapi官方文档">

    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" media="screen">
    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css" media="screen">
    <link rel="stylesheet" href="../css/main.css" />
    <link rel="stylesheet" href="../css/loading_bar.css" />
    <link rel="icon" href="http://webtools.qiniudn.com/dog_catch.png" type="image/x-icon" />
    <script src="../js/pace.min.js"></script>
	
	<!-- 代码高亮 -->
	<link rel="stylesheet" href="../css/highlight.min.css">
	<script src="../js/highlight.min.js"></script>
	<script>hljs.initHighlightingOnLoad();</script>
    
</head>

<body>
    <!-- navbar start -->
    <div class="navbar navbar-default navbar-fixed-top" id="mainnav" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand website_name" href="/"><!-- <span>PhalApi</span> --><strong>PhalApi</strong></a>
            </div>
            <div class="navbar-collapse collapse" aria-expanded="false" style="height: 1px;">
                <ul class="nav navbar-nav navbar-right" id="nav_bar">
                    <li><a href="/"><span class="glyphicon glyphicon-home"></span></a></li>
                    <li><a href="http://qa.phalapi.net/">社区</a></li>
                    <li><a href="/download.html">下载</a></li>
                    <li class="active"><a href="/wikis/">文档</a></li>
                    <li><a href="http://www.ituring.com.cn/book/2405">书籍</a></li>
                    <li><a href="http://demo.phalapi.net/">体验</a></li>
                    <li><a href="/about.html">关于</a></li>
                    <li><a href="/donate.html">贡献</a></li>
                    <li><a href="/wikis/en/">English</a></li>
                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
    <!-- navbar end -->


    <div id="content">
        <div class="container">
            <div class="row row-md-flex row-md-flex-wrap">
                <h4><a href="/wikis/%5B2.16%5D-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%EF%BC%9A%E5%BA%94%E5%AF%B9%E5%A4%8D%E6%9D%82%E9%A2%86%E5%9F%9F%E4%B8%9A%E5%8A%A1%E7%9A%84Domain%E5%B1%82.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B2.17%5D-%E5%BE%AE%E6%9C%8D%E5%8A%A1%EF%BC%9AApi%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%E5%B1%82.html">下一章</a></h4>
<hr />
<p><em>我发现，我越是努力，就越发幸运。 -- Thomas Jefferson</em></p>
<h2>2.17.1 微服务</h2>
<p><img src="http://7qnay5.com1.z0.glb.clouddn.com/qq_20150607110405.jpg" alt="a pic" />  </p>
<p>Martin Fowler（我喜欢和敬仰的大师）曾发表了上面这一段话。这段话也出现在了2015年QCon分享会上，并加了一张PPT“什么是微服务”加以说明。 </p>
<p><img src="http://7qnay5.com1.z0.glb.clouddn.com/qcon_20150607.png" alt="a pic" />   </p>
<p>里面提到了 <strong>微服务</strong> 这个概念，在PhalApi框架中即对应我们的Api接口服务层，只是我们不是称之为微服务，而是接口服务。<br />
不管何种说法，我们都应该关注里面提及到的这几点重要特质：  </p>
<ul>
<li>小，且专注于做一件事情</li>
<li>独立的进程中</li>
<li>轻量级的通信机制</li>
<li>松耦合、独立部署</li>
</ul>
<p>这里不过多地讨论微服务相关的分享，而是重温接口服务层Api与领域驱动和单元测试之间的关系，以及如何开发一个优雅、稳定又简单的接口。  </p>
<h2>2.17.2 层级调用的顺序</h2>
<p>整体上讲根据从Api接口层、Domain领域层再到Model数据源层的顺序进行开发。  </p>
<p>在开发过程中，需要注意不能 <strong>越层调用</strong> 也不能 <strong>逆向调用</strong> ，即不能Api调用Model。而应该是 <strong>上层调用下层，或者同层级调用</strong> ，也就是说，我们应该：</p>
<ul>
<li>Api层调用Domain层</li>
<li>Domain层调用Domain层</li>
<li>Domain层调用Model层</li>
<li>Model层调用Model层</li>
</ul>
<p>如果用一张图来表示，则是：<br />
<img src="http://7qnay5.com1.z0.glb.clouddn.com/201506071124.png" alt="a pic" />  </p>
<p>为了更明确调用的关系，以下调用是 <strong>错误</strong> 的：  </p>
<ul>
<li>错误的做法1：Api层直接调用Model层</li>
<li>错误的做法2: Domain层调用Api层，也不应用将Api层对象传递给Domain层</li>
<li>错误的做法3: Model层调用Domain层 </li>
</ul>
<p>这样的约定，便于我们形成统一的开发规范，降低学习维护成本。<br />
比如需要添加缓存，我们知道应该定位到Model层数据源进行扩展；若发现业务规则处理不当，则应该进入Domain层探其究竟；如果需要对接口的参数进行调整，即使是新手也知道应该找到对应的Api文件进行改动。  </p>
<h2>2.17.3 接口服务的定义</h2>
<p>现实项目开发过程中，绝大部分我们编写的接口是给别人使用的，或许给Android客户端同学使用，或者给iOS客户端同学使用，抑或提供给其他后台系统的同学使用。<br />
为了提高并行开发的速度，我们不能等待接口完全开发完成后才提供接口文档，而且他们也不能忍受这么漫长的等待。   </p>
<p>所以，客户端同学时常会问我们：什么时候可以提供接口文档？  </p>
<p>我们提倡“接口先行”，如果有时不能很好地做到这一点（毕竟多变的需求促发多变的情境），我们可以快速提供接口的定义。<br />
这有点像规约层对接口的定义一样，在PhalApi中定义一个接口，再具体一点即：  </p>
<ul>
<li>1、创建一个接口服务类和声明函数</li>
<li>2、配置接口参数规则</li>
<li>3、提供接口返回格式的注释</li>
</ul>
<p>简单来说，就是创建一个类，写个函数，定义参数和返回结果。  </p>
<p>下面以 <a href="http://git.oschina.net/dogstar/PhalApi-Demo-Vote">开发实战3：一个简单的小型项目开发（奔跑吧兄弟投票活动）</a> 中的团队参赛接口为例，说明这三步操作的过程。  </p>
<h3>(1)创建一个接口服务类和声明函数</h3>
<pre><code class="language-php">//$ vim ./Vote/Api/Act.php 

&lt;?php
class Api_Act extends PhalApi_Api {

    public function joinIn() {
    }
}</code></pre>
<h3>(2)配置接口参数规则</h3>
<pre><code class="language-php">&lt;?php
class Api_Act extends PhalApi_Api {

    public function getRules() {
        return array(
            'joinIn' =&gt; array(
                'teamName' =&gt; array('name' =&gt; 'team_name', 'require' =&gt; true, 'min' =&gt; 1, 'max' =&gt; 100),
            ),
        );
    }

    public function joinIn() {
    }
}</code></pre>
<h3>(3)提供接口返回格式的注释</h3>
<pre><code class="language-php">&lt;?php
class Api_Act extends PhalApi_Api {

    public function getRules() {
        return array(
            'joinIn' =&gt; array(
                'teamName' =&gt; array('name' =&gt; 'team_name', 'require' =&gt; true, 'min' =&gt; 1, 'max' =&gt; 100),
            ),
        );
    }

    /**
     * 团队参赛接口
     *  
     * @return int code 0，参赛成功；1，队名已存在
     * @return int team_id 新建的团队ID
     */
    public function joinIn() {
    }
}</code></pre>
<h3>(4)在线查看一下效果</h3>
<p>在完成上面的动作后，我们可以通过在线工具来看下实时的效果，在浏览打开后访问：</p>
<pre><code>http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.JoinIn</code></pre>
<p>可以看到：<br />
<img src="http://7qnay5.com1.z0.glb.clouddn.com/201506071302.jpg" alt="a pic" />  </p>
<p>到了这里，即使我们未完成接口的开发，也未提供更完善的接口文档，但接口客户端同学也可以根据这个在线的接口参数进行开发了。</p>
<h2>2.17.4 在ATDD下讲述故事</h2>
<p>我们一直推崇测试驱动开发，但在对于Api接口开发，更准确来说是ATDD，即：验收测试驱动开发（Acceptance Test Driven Development）。  </p>
<p>在前面Domain层文档中，我们提到了Api层是讲述故事的场景。因此，为了验证我们的业务场景是否正确，我们应该事先编写好单元测试，以不断引导我们前往正确的目的地。  </p>
<p>我们可以使用脚本来快速生成测试骨架：</p>
<pre><code class="language-bash">$ pwd
$ /path/to/api.vote.phalapi.com/Vote/Tests/Api
$ phalapi-buildtest ../../Api/Act.php Api_Act ../test_env.php &gt; Api_Act_Test.php</code></pre>
<p>然后，稍微修改完善测试场景：</p>
<pre><code class="language-php">    /**
     * @group testJoinIn
     */
    public function testJoinIn()
    {
        //Step 1. 构建请求URL
        $url = 'service=Act.JoinIn';
        $params = array(
            'sign' =&gt; 'phalapi',
            'team_name' =&gt; 'test team name',
            'user_id' =&gt; '1',
            'token' =&gt; '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731',
        );

        DI()-&gt;notorm-&gt;team-&gt;where('team_name', $params['team_name'])-&gt;delete();

        //Step 2. 执行请求  
        $rs = PhalApi_Helper_TestRunner::go($url, $params);
        //var_dump($rs);

        //Step 3. 验证
        $this-&gt;assertNotEmpty($rs);
        $this-&gt;assertArrayHasKey('code', $rs);
        $this-&gt;assertArrayHasKey('team_id', $rs);
        $this-&gt;assertEquals(0, $rs['code']);
        $this-&gt;assertGreaterThan(0, $rs['team_id']);

        //create again
        $rs = PhalApi_Helper_TestRunner::go($url, $params);
        $this-&gt;assertEquals(1, $rs['code']);
    }
</code></pre>
<p>从上面测试的代码可以看出，我们先后进行了两次报名。明显地，第一次报名应该是成功的，第二次则应该提示不能重复报名。  </p>
<p>单元测试的好处，不但在于可以引导我们做正确的事情，还可以提高我们的关注点，不致于在开发过程中被各种事务（如临时性的会议）打断后回来却不知刚才开发到哪了。<br />
然而，更多的是为后期的维护、扩展提供可验证的业务场景。这点是很重要的。因为每一个测试场景，都保存了对应场景的模拟信息，这样不仅仅在后面的扩展，还是突如其来的BUGFIXED，我们都可以快速证明我们的修改是正确的，至少不会影响到原来的业务流程。<br />
试想一下，如果原来可以下单支付的接口，突然被影响到而导致支付不成功，这是何等的损失！  </p>
<p>我现在慢慢地，每当需要修改别人的代码时，我都会看下有没有对应的单元测试。如果没有，我会先补回，这样能增强我修改别人代码的信心。  </p>
<h2>2.17.5 精益接口开发</h2>
<p>传统的接口开发，由于没有很好的分层结构，而且热衷于在一个文件里面完成绝大部分事情，最终导致了臃肿漫长的代码，也就是通常所说的意大利面条式的代码。  </p>
<p>在PhalApi中，我们针对接口领域开发，提供了新的分层思想：ADM（Api - Domain - Model）。<br />
即便这样，如果项目在实际开发中，仍然使用原来的做法，纵使使用再好的接口开发框架，也还是会退化到原来的局面。  </p>
<p>为了能让大家更为明确Api接口层的职责所在，我们建议：  </p>
<h3>(1)Api接口层应该做：</h3>
<ul>
<li>应该：对用户登录态进行必要的检测</li>
<li>应该：控制业务场景的主流程，创建领域业务实例，并进行调用</li>
<li>应该：进行必要的日记纪录</li>
<li>应该：返回接口结果</li>
</ul>
<h3>(2)Api接口层不应该做：</h3>
<ul>
<li>不应该：进行业务规则的处理或者计算</li>
<li>不应该：关心数据是否使用缓存，或进行缓存相关的直接操作</li>
<li>不应该：直接操作数据库</li>
<li>不应该：将多个接口合并在一起</li>
</ul>
<p>在明确了上面应该做的和不应该做的，并且也完成了接口的定义，还有验收测序驱动开发的场景准备后，相信这时，即使是新手也可以编写出高质量的接口代码。<br />
因为他会受到约束，他知道他需要做什么，主要他按照限定的开发流程和约定稍加努力即可。  </p>
<p>如果真的这样，相信我们也就慢慢能体会到 <strong>精益开发</strong> 的乐趣。  </p>
<p>最后，让我们一起来看下上述团队参赛接口开发的代码实现：</p>
<pre><code class="language-php">    /**
     * 团队参赛接口
     *  
     * @return int code 0，参赛成功；1，队名已存在
     * @return int team_id 新建的团队ID
     */
    public function joinIn() {
        $rs = array('code' =&gt; 0, 'team_id' =&gt; 0);

        DI()-&gt;userLite-&gt;check(true);

        $domain = new Domain_Team();
        if ($domain-&gt;isExists($this-&gt;teamName)) {
            $rs['code'] = 1;
            return $rs;
        }

        $teamId = $domain-&gt;joinIn($this-&gt;teamName);
        $rs['team_id'] = $teamId;

        return $rs;
    }
</code></pre>
<p>可以看出，上面的代码短小达意，简单清晰。</p>
<hr />
<h4><a href="/wikis/%5B2.16%5D-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%EF%BC%9A%E5%BA%94%E5%AF%B9%E5%A4%8D%E6%9D%82%E9%A2%86%E5%9F%9F%E4%B8%9A%E5%8A%A1%E7%9A%84Domain%E5%B1%82.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B2.17%5D-%E5%BE%AE%E6%9C%8D%E5%8A%A1%EF%BC%9AApi%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%E5%B1%82.html">下一章</a></h4><div style="float: left">
<h4>
<a href="http://qa.phalapi.net/">还有疑问？欢迎到社区提问！</a>
</h4>
</div>
            </div>
        </div>
    </div>

 <!-- 广告位 -->
<div class="grid-wrapper desktop-only">
	<p align="center">
		<a href="http://7xiz2f.com1.z0.glb.clouddn.com/%E6%88%91%E7%9A%84%E5%90%8D%E5%AD%97%E5%8F%AB%EF%BC%9A%CF%80%E6%A1%86%E6%9E%B6%20-%20PhalApi%202016%E5%B9%B4%E5%BA%A6%E5%BC%80%E6%BA%90%E6%80%BB%E7%BB%93%20-%20%E5%AE%98%E6%96%B9%E5%87%BA%E5%93%81.pdf" target="blank"><img width="950" height="100" src="http://7xiz2f.com1.z0.glb.clouddn.com/ad_20170104.png"></a>
		<a href="http://www.itran.cc/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/9/69/b88bc92455dc1239f9a5bf8d72929.png"></a>
		<a href="http://web-tools.phalapi.net/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/7/d0/88f523566c482296aecc43d185ca2.png"></a>
	</p>
</div>

        <!-- footer -->
        <div class="footer">
            <div class="link container ">
                <div class=" col-xs-7 col-sm-7 col-md-7 col-lg-7 ">
                    <h3>PhalApi (π框架)</h3>
                    <p>一个轻量级PHP开源接口框架，专注于接口服务开发，支持HTTP/SOAP/RPC协议，拥有自动生成的在线文档、多种开发语言的客户端SDK包以及可重用的扩展类库，可用于快速搭建微服务、RESTful接口或Web
                        Services。</p>
                    <!-- <p>:</p> -->
                    <span>友情链接:</span>
                    <a href="https://www.phalapi.net/" target="_blank"> PhalApi </a>
                    <a href="http://www.oschina.net/" target="_blank"> 开源中国 </a>
                    <a href="https://www.itran.cc/" target="_blank">艾翻译</a>
                    <a href="http://www.phalconphp.com/en/" target="_blank">Phalcon</a>
                    <a href="https://phpunit.de/manual/3.7/zh_cn/automating-tests.html" target="_blank">PHPUnit</a>
                    <a href="http://www.thoughtworks.com/cn/" target="_blank">ThoughtWorks</a>
                    <a href="mailto:chanzonghuang@gmail.com">友链交换</a>

                </div>
                <div class=" col-xs-5 col-sm-5 col-md-5 col-lg-5 contact_us ">
                    <h3>联系我们</h3>
                    <a href="https://github.com/phalapi/phalapi"><img src="../images/github.png" alt="github" /></a>
                    <a href="http://weibo.com/p/100808d236e99beb645bfb56ed1c37dde9b8bd?k=phalapi%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6&from=501&_from_=huati_topic"><img src="../images/weibo.png"  alt="weibo" /></a>
                    <a href="http://7xslqv.com1.z0.glb.clouddn.com/images/1/b0/894fde762850cb11250253f7d5ede.png"><img src="../images/zfb.png"  alt="zfb" /></a>
                    <a href="https://jq.qq.com/?_wv=1027&k=4A6reum"><img src="../images/qq.png"  alt="qq" /></a>
					<a href="http://git.oschina.net/dogstar/PhalApi"><img src="../images/git-oschina.png"  alt="git-oschina" /></a>
                </div>
                <div class="cls"></div>
                <div class="text-center copy_right"> ©2015-2017 PhalApi All Rights Reserved. <a href="http://www.miitbeian.gov.cn" target="_blank">粤ICP备15028808号</a>

				<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1255326144'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/z_stat.php%3Fid%3D1255326144%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));</script>
                </div>
            </div>
        </div>
    </div>


    <a href="https://github.com/phalapi"><img id="gitHub_fllow" style="position: fixed; top: 0; right: 0; border: 0;z-index:9999" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>


    <script src="../js/jquery-1.11.1.min.js"></script>
    <script src="../js/bootstrap.min.js"></script>
</body>

</html>